<script setup lang="ts">
import {CSSProperties, ref} from 'vue'
import {TreeOption, NTree, TreeProps, NInput, InputProps, NCheckbox, CheckboxProps, NSpace, SpaceProps} from 'naive-ui'
import {StnScrollbar} from 'st-ui-vue3-navie'
import {arrCopyDeep} from 'st-common-core'

/**
 * 组件配置选项
 */
defineOptions({
  name: 'StnTreeCheck',
})

/**
 * 组件参数
 */
const props = withDefaults(
  defineProps<{
    // 选择树区域的高度
    treeHeight?: number | string;
    // 选择树区域的最大高度
    treeMaxHeight?: number | string;
    // 树选项配置
    treeOptions?: TreeOption[];
    // 树区域的样式
    treeClass?: string | string[];
    // 树区域的样式类名
    treeStyle?: string | CSSProperties;
    // 替代 TreeOption 中的 children 字段名
    childrenField?: string;
    // 替代 TreeOption 中的 key 字段名
    keyField?: string;
    // Tree 组件 props
    treeProps?: Partial<TreeProps>;
    // 是否开启搜索
    searchable?: boolean;
    // 搜索输入框大小
    searchInputSize?: 'small' | 'medium' | 'large';
    // 搜索输入框占位符
    searchInputPlaceholder?: string;
    // 搜索输入框 props
    searchInputProps?: Partial<InputProps>;
    // 复选框 Space 组件 props
    checkboxSpaceProps?: Partial<SpaceProps>;
    // 是否启用级联选择
    cascadeCheckBox?: boolean;
    // 级联选择复选框文本
    cascadeCheckBoxText?: string;
    // 级联选择复选框 props
    cascadeCheckBoxProps?: Partial<CheckboxProps>;
    // 是否启用展开/折叠
    expandCheckBox?: boolean;
    // 展开/折叠复选框文本
    expandCheckBoxText?: string;
    // 展开/折叠复选框 props
    expandCheckBoxProps?: Partial<CheckboxProps>;
    // 是否启用全选
    checkAllCheckBox?: boolean;
    // 全选复选框文本
    checkAllCheckBoxText?: string;
    // 全选复选框 props
    checkAllCheckBoxProps?: Partial<CheckboxProps>;
  }>(),
  {
    treeHeight: 'auto',
    treeMaxHeight: 'none',
    treeOptions: [] as any,
    childrenField: 'children',
    keyField: 'key',
    treeProps: {} as any,
    searchable: true,
    searchInputSize: 'small',
    searchInputPlaceholder: '请输入搜索关键词',
    searchInputProps: {} as any,
    checkboxSpaceProps: {} as any,
    cascadeCheckBox: true,
    cascadeCheckBoxText: '级联选择',
    cascadeCheckBoxProps: {} as any,
    expandCheckBox: true,
    expandCheckBoxText: '展开/折叠',
    expandCheckBoxProps: {} as any,
    checkAllCheckBox: true,
    checkAllCheckBoxText: '全选/全不选',
    checkAllCheckBoxProps: {} as any,
  }
)

/**
 * 组件双向数据绑定
 */
// 选中项主键列表
const value = defineModel<(string | number)[]>('value', {default: []})
// 展开节点主键列表
const expandedKeys = defineModel<(string | number)[]>('expandedKeys', {default: []})

/**
 * 搜索匹配关键词
 */
const pattern = ref('')

/**
 * 级联选择
 */
const cascade = ref(false)

/**
 * 展开/折叠
 */
const expand = ref(false)
const expandChangeHandler = (newExpand: boolean) => {
  expand.value = newExpand
  // 清空展开节点主键列表
  expandedKeys.value = []
  // 折叠所有节点
  if (!newExpand) return
  // 展开所有可展开的节点
  arrCopyDeep({
    origin: props.treeOptions,
    props: [props.childrenField],
    targetPropValMap: {
      [props.childrenField]: (origin) => {
        // 如果当前节点可展开，则展开并添加到展开节点主键列表
        if (Array.isArray(origin[props.childrenField]) && origin[props.childrenField].length > 0) {
          expandedKeys.value.push(origin[props.keyField])
        }
        return origin[props.childrenField]
      },
    }
  })
}

/**
 * 全选
 */
const checkAll = ref(false)
const checkAllChangeHandler = (newCheckAll: boolean) => {
  checkAll.value = newCheckAll
  // 清空选中项主键列表
  value.value = []
  // 全不选
  if (!newCheckAll) return
  // 选中项主键列表
  const checkedKeyList = []
  // 遍历所有节点
  arrCopyDeep({
    origin: props.treeOptions,
    props: [props.keyField, props.childrenField],
    targetPropValMap: {
      [props.keyField]: (origin) => {
        // 将当前节点主键添加到选中项主键列表中
        checkedKeyList.push(origin[props.keyField])
        return origin[props.keyField]
      },
    }
  })
  // 更新选中项主键列表
  value.value = checkedKeyList
}
</script>

<template>
  <div class="stn-tree-check">
    <n-input
      v-if="searchable"
      class="stn-tree-check__search-input"
      v-bind="{
        ...searchInputProps,
        size: searchInputSize,
        placeholder: searchInputPlaceholder,
        clearable: true,
        value: pattern,
        'onUpdate:value': (newPattern: string) => pattern = newPattern.trim(),
      }"
    />
    <n-space
      v-if="cascadeCheckBox || expandCheckBox || checkAllCheckBox"
      class="stn-tree-check__checkbox-space"
      v-bind="{
        justify: 'space-between',
        align: 'center',
        ...checkboxSpaceProps,
      }"
    >
      <n-checkbox
        v-if="cascadeCheckBox"
        v-bind="{
          ...cascadeCheckBoxProps,
          checked: cascade,
          'onUpdate:checked': (newCascade: boolean) => cascade = newCascade,
          label: cascadeCheckBoxText,
        }"
      />
      <n-checkbox
        v-if="expandCheckBox"
        v-bind="{
          ...expandCheckBoxProps,
          checked: expand,
          'onUpdate:checked': expandChangeHandler,
          label: expandCheckBoxText,
        }"
      />
      <n-checkbox
        v-if="checkAllCheckBox"
        v-bind="{
          ...checkAllCheckBoxProps,
          checked: checkAll,
          'onUpdate:checked': checkAllChangeHandler,
          label: checkAllCheckBoxText,
        }"
      />
    </n-space>
    <StnScrollbar
      class="stn-tree-check__tree-scrollbar"
      :class="treeClass"
      :style="treeStyle"
      :height="treeHeight"
      :max-height="treeMaxHeight"
    >
      <n-tree
        class="stn-tree-check__tree"
        v-bind="{
        blockLine: true,
        blockNode: true,
        expandOnClick: true,
        showLine: true,
        showIrrelevantNodes: false,
        ...treeProps,
        data: treeOptions,
        childrenField: childrenField,
        checkable: true,
        pattern: pattern,
        cascade: cascade,
      }"
        v-model:checked-keys="value"
        v-model:expanded-keys="expandedKeys"
      />
    </StnScrollbar>
  </div>
</template>

<style scoped lang="scss">
.stn-tree-check {
  width: 100%;

  .stn-tree-check__search-input {
    margin-bottom: 0.5rem;
  }

  .stn-tree-check__checkbox-space {
    margin-bottom: 0.25rem;
  }

  .stn-tree-check__tree-scrollbar {
    width: 100%;
  }
}
</style>
